package com.lzp.controller;

import com.lzp.pojo.Users;
import com.lzp.pojo.vo.UsersVO;
import com.lzp.service.UserService;
import com.lzp.utils.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Controller
public class SSOController {

    /**
     * Redis前缀
     */
    public static final String REDIS_USER_TOKEN = "redis_user_token";
    public static final String REDIS_USER_TICKET = "redis_user_ticket";
    public static final String REDIS_TMP_TICKET = "redis_tmp_token";
    /**
     * Cookie
     */
    public static final String COOKIE_USER_TICKET = "cookie_user_ticket";

    @Autowired
    private UserService userService;

    @Autowired
    private RedisOperator redis;

    /**
     * 用户登录，如果CAS验证用户未登录，跳转CAS单点登录界面
     * <p>
     * 实现登录业务系统，验证登录等功能
     *
     * @return
     */
    @GetMapping("/login")
    // @ResponseBody 使用thymeleaf不能使用该注解
    public Object login(String returnUrl,
                        Model model,
                        HttpServletRequest request,
                        HttpServletResponse response) {
        model.addAttribute("returnUrl", returnUrl); // 回调地址
        // 1. 从CAS端cookie中获取userTiket全局门票，如果存在表示用户登录过，则签发一次性临时票据tmpTicket
        String userTicket = getCookie(request, COOKIE_USER_TICKET);

        boolean isVerified = verifyUserTicket(userTicket);
        if (isVerified) {
            // 校验通过，生成临时门票
            String tmpTicket = createTmpTicket();
            return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;
        }

        // 2 用户从未登录过，第一次进入跳转到CAS统一登录界面
        return "login"; // login对应html文件名
    }

    /**
     * 验证全局门票
     *
     * @param userTicket
     * @return
     */
    private boolean verifyUserTicket(String userTicket) {
        // 校验全局门票非空
        if (StringUtils.isBlank(userTicket)) {
            return false;
        }
        // 校验门票对应用户id
        String userId = redis.get(REDIS_USER_TICKET + ":" + userTicket);
        if (StringUtils.isBlank(userId)) {
            return false;
        }
        // 校验用户会话信息
        String userRedis = redis.get(REDIS_USER_TOKEN + ":" + userId);
        if (StringUtils.isBlank(userRedis)) {
            return false;
        }

        return true;
    }

    /**
     * CAS登录操作
     * <p>
     * 目的：
     * 1. 登录后创建用户全局会话 --- uniqueToken
     * 2. 创建用全局门票，表示CAS端是否登录 -- userTicket
     * 3. 创建用临时票据，用于回跳回传 -- tmpTicket
     *
     * @return
     */
    @PostMapping("/doLogin")
    public Object doLogin(String username,
                          String password,
                          String returnUrl,
                          Model model,
                          HttpServletRequest request,
                          HttpServletResponse response) throws Exception {
        // 校验用户名和密码不为空
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            model.addAttribute("errmsg", "用户名或密码不能为空");
            return "login";
        }

        // 1 校验用户名和密码是否正确
        Users user = userService.queryUserForLogin(username, MD5Utils.getMD5Str(password));
        if (user == null) {
            model.addAttribute("errmsg", "用户名或密码不正确");
            return "login";
        }

        // 2 实现用户的Redis会话
        String uniqueToken = UUID.randomUUID().toString().trim();
        UsersVO usersVO = new UsersVO();
        BeanUtils.copyProperties(user, usersVO);
        usersVO.setUserUniqueToken(uniqueToken);
        redis.set(REDIS_USER_TOKEN + ":" + user.getId(), JsonUtils.objectToJson(usersVO));

        // 3 生成ticket门票--全局门票，代表用户在CAS系统中登录过。
        String userTicket = UUID.randomUUID().toString().trim();
        redis.set(REDIS_USER_TICKET + ":" + userTicket, user.getId());
        // 3.1 全局门票需要放到CAS端Cookie中
        setCookie(response, userTicket, COOKIE_USER_TICKET);

        // 4生成临时票据，回跳到调用网站，由CAS端签发生成的一次性临时Ticket，具有时效性。
        String tmpTicket = createTmpTicket();
        return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;

        /**
         * 解释：
         * 我们去灵隐寺景区玩需要购买门票，即CAS的全局门票
         * 但是参观灵隐寺和飞来峰需要凭门票单独购买一次性门票，即有了临时票据，才能访问某个业务系统
         * 临时票据具有时效性，并且用完销毁。
         */
        // return "login";
    }

    /**
     * 回调用户站点后，再由站点发起验证临时票据请求
     *
     * @param tmpTicket
     * @param request
     * @param response
     * @return
     */
    @PostMapping("/verifyTmpTicket")
    @ResponseBody
    public ServerResponse verifyTmpTicket(String tmpTicket,
                                          HttpServletRequest request,
                                          HttpServletResponse response) throws Exception {
        // 1. 从Redis中验证用户票据，判断是否登录过
        String tmpTicketVal = redis.get(REDIS_TMP_TICKET + ":" + tmpTicket);
        if (StringUtils.isBlank(tmpTicketVal)) {
            return ServerResponse.createByErrorMsg("用户票据异常");
        }
        // 如果临时票据验证通过，则进行销毁。并换取CAS端Cookie中的全局门票，从而获取用户信息
        if (!tmpTicketVal.equals(MD5Utils.getMD5Str(tmpTicket))) {  // 仅用于演示校验
            return ServerResponse.createByErrorMsg("用户票据异常");
        } else {
            // 销毁临时票据
            redis.del(REDIS_TMP_TICKET + ":" + tmpTicket);
        }

        // 2 验证并从CAS端Cookie中获取全局门票 userTicket
        String userTicket = getCookie(request, COOKIE_USER_TICKET);
        // 再根据userTicket从Redis中获取用户id
        String userId = redis.get(REDIS_USER_TICKET + ":" + userTicket);
        if (StringUtils.isBlank(userId)) {
            return ServerResponse.createByErrorMsg("用户票据异常");
        }

        // 3 验证门票对应的用户会话是否存在
        String userRedis = redis.get(REDIS_USER_TOKEN + ":" + userId);
        if (StringUtils.isBlank(userRedis)) {
            return ServerResponse.createByErrorMsg("用户票据异常");
        }

        // 验证成功，返回200，携带用户会话(转换为POJO)
        return ServerResponse.createBySuccess(JsonUtils.jsonToPojo(userRedis, UsersVO.class));
    }

    
    @PostMapping("/logout")
    @ResponseBody
    public ServerResponse logout(String userId,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        // 获取用户门票
        String userTicket = getCookie(request, COOKIE_USER_TICKET);
        // 删除cookie
        deleteCookie(COOKIE_USER_TICKET, response);
        // 删除门票
        redis.del(REDIS_USER_TICKET + ":" + userTicket);
        // 删除用户会话
        redis.del(REDIS_USER_TOKEN + ":" + userId);
        return ServerResponse.createBySuccess();
    }

    private void deleteCookie(String cookieUserTicket, HttpServletResponse response) {
        Cookie cookie = new Cookie(cookieUserTicket, null);
        cookie.setDomain("sso.com");
        cookie.setPath("/");
        cookie.setMaxAge(-1);
        response.addCookie(cookie);
    }

    private void setCookie(HttpServletResponse response,
                           String val,
                           String key) {
        Cookie cookie = new Cookie(key, val);
        cookie.setDomain("sso.com");
        cookie.setPath("/");
        response.addCookie(cookie);
    }

    /**
     * 从cookie中获取值
     *
     * @param request
     * @param userTicketKey
     * @return
     */
    private String getCookie(HttpServletRequest request, String userTicketKey) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null || StringUtils.isBlank(userTicketKey)) {
            return null;
        }

        String userTicketVal = null;
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].equals(userTicketKey)) {
                userTicketVal = cookies[i].getValue();
                break;
            }
        }
        return userTicketVal;
    }

    /**
     * 创建临时票据
     *
     * @return
     */
    private String createTmpTicket() {
        String tmpTicket = UUID.randomUUID().toString().trim();
        try {
            // 临时门票存到Redis中
            redis.set(REDIS_TMP_TICKET + ":" + tmpTicket, MD5Utils.getMD5Str(tmpTicket), 600);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return tmpTicket;
    }

}
